﻿using System.Collections.Generic;

namespace Stimik
{
    class Rubiks
    {
        //ukuran rubik(2 menandakan 2x2, dst)
        int size;

        //konstanta operasi rubiks
        public const int RIGHTC = 1;
        public const int RIGHTCC = 2;
        public const int UPC = 3;
        public const int UPCC = 4;
        public const int FACEC = 5;
        public const int FACECC = 6;

        public int Size
        {
            get { return size; }
        }

        //matrix sisi rubiks (sisi dipandang dari rubiks yang dibuka menjadi 2D)
        char[,] up;
        char[,] down;
        char[,] face;
        char[,] back;
        char[,] left;
        char[,] right;

        public char[,] Up
        {
            set { up = value; }
            get { return up; }
        }
        public char[,] Down
        {
            set { down = value; }
            get { return down; }
        }
        public char[,] Face
        {
            set { face = value; }
            get { return face; }
        }
        public char[,] Back
        {
            set { back = value; }
            get { return back; }
        }
        public char[,] Left
        {
            set { left = value; }
            get { return left; }
        }
        public char[,] Right
        {
            set { right = value; }
            get { return right; }
        }

        public Rubiks(int size, char[,] top, char[,] down, char[,] face, char[,] back, char[,] left, char[,] right)
        {
            this.up = new char[size,size];
            this.down = new char[size,size];
            this.face = new char[size,size];
            this.back = new char[size,size];
            this.left = new char[size,size];
            this.right = new char[size,size];
            this.size = size;
            for (int i = 0; i < size; i++)
            {
                for (int j = 0; j < size;j++)
                {                    
                    this.up[i,j] = top[i,j];
                    this.down[i,j] = down[i,j];
                    this.face[i,j] = face[i,j];
                    this.back[i,j] = back[i,j];
                    this.left[i,j] = left[i,j];
                    this.right[i,j] = right[i,j];
                }
            }
        }

        //memutar kolom kanan rubiks ini searah jarum jam(jika diliat dari face, berarti ke atas)
        public void opRight()
        {
            char[,] tempup = new char[size, size];
            copymat(up, tempup);            
            for(int i=0;i<size;i++) 
            {
                up[i, size - 1] = face[i, size - 1];
                face[i, size - 1] = down[i, size - 1];
                down[i, size - 1] = back[size-i-1, 0];                
                back[size-i-1,0] = tempup[i,size-1];
            }
            rotatemat(right);
        }        

        //membuat rubiks yang dihasilkan dari putaran kolom kanan searah jarum jam dari rubiksi ini 
        public Rubiks getTurnRight(bool cc=false)
        {
            Rubiks r = createRubikCopy(this);
            r.opRight();
            if(cc) {
                r.opRight();
                r.opRight();
            }
            return r;
        }

        //memutar kolom kiri rubiks ini searah jarum jam(jika diliat dari face, berarti ke bawah)
        public void opLeft()
        {
            char[,] tempdown = new char[size, size];
            copymat(down, tempdown);            
            for (int i = 0; i < size;i++ )
            {
                down[i, 0] = face[i, 0];
                face[i, 0] = up[i, 0];
                up[i, 0] = back[size-i-1, size-1];
                back[size-i-1, size-1] = tempdown[i, 0];
            }
            rotatemat(left);
        }

        //membuat rubiks yang dihasilkan dari putaran kolom kiri searah jarum jam dari rubiksi ini 
        public Rubiks getTurnLeft()
        {
            Rubiks r = createRubikCopy(this);
            r.opLeft();
            return r;
        }

        //memutar sisi muka rubiks ini searah jarum jam(jika diliat dari face, berarti ke kanan)
        public void opFace()
        {
            char[,] templeft = new char[size, size];
            copymat(left,templeft);            
            for (int i = 0; i < size; i++)
            {
                left[i,size-1] = down[0,i];
                down[0,i] = right[size-i-1,0];
                right[size - i - 1, 0] = up[size - 1, size - i - 1];
                up[size-1, size-i-1] = templeft[i,size-1];                
            }
            rotatemat(face);
        }

        //membuat rubiks yang dihasilkan dari putaran sisi muka searah jarum jam dari rubiksi ini 
        public Rubiks getTurnFace(bool cc=false)
        {
            Rubiks r = createRubikCopy(this);
            r.opFace();
            if(cc) {
                r.opFace();
                r.opFace();
            }
            return r;
        }

        //memutar sisi belakang rubiks ini searah jarum jam(jika diliat dari face, berarti ke kiri)
        public void opBack()
        {
            char[,] tempright = new char[size, size];
            copymat(right,tempright);            
            for(int i=0;i<size;i++) 
            {                
                right[i,size-1] = down[size-1,size-i-1];
                down[size-1,size-i-1] = left[size-i-1,0];
                left[size-i-1,0] = up[0,i];
                up[0,i] = tempright[i,size-1];
            }
            rotatemat(back);
        }

        //membuat rubiks yang dihasilkan dari putaran sisi belakang searah jarum jam dari rubiksi ini 
        public Rubiks getTurnBack()
        {
            Rubiks r = createRubikCopy(this);
            r.opBack();
            return r;
        }

        //memutar sisi atas rubiks ini searah jarum jam(jika diliat dari face, berarti ke kiri)
        public void opUp()
        {
            char[,] templeft = new char[size, size];
            copymat(left,templeft);            
            for (int i = 0; i < size; i++)
            {
                left[0,i] = face[0,i];
                face[0,i] = right[0,i];
                right[0,i] = back[0,i];
                back[0,i] = templeft[0,i];
            }
            rotatemat(up);
        }

        //membuat rubiks yang dihasilkan dari putaran sisi atas searah jarum jam dari rubiksi ini 
        public Rubiks getTurnUp(bool cc=false)
        {
            Rubiks r = createRubikCopy(this);
            r.opUp();
            if(cc) {
                r.opUp();
                r.opUp();
            }
            return r;
        }

        //memutar sisi bawah rubiks ini searah jarum jam(jika diliat dari face, berarti ke kanan)
        public void opDown()
        {
            char[,] tempright = new char[size, size];
            copymat(right, tempright);            
            for (int i = 0; i < size; i++)
            {
                right[size - 1, i] = face[size - 1, i];
                face[size-1,i] = left[size-1,i];
                left[size - 1, i] = back[size - 1, size - i - 1];
                back[size-1, size-i-1] = tempright[size-1,size-i-1];
            }
            rotatemat(down);
        }

        //membuat rubiks yang dihasilkan dari putaran sisi bawah searah jarum jam dari rubiksi ini 
        public Rubiks getTurnDown()
        {
            Rubiks r = createRubikCopy(this);
            r.opDown();
            return r;
        }

        //algoritma ngasal, hanya berlaku untuk 2x2
        public bool isEqual(Rubiks r)
        {
            int[] sumside1 = new int[6];
            int[] sumside2 = new int[6];
            List<int> sidecheck = new List<int>();
            sidecheck.Add(0);sidecheck.Add(1);sidecheck.Add(2);sidecheck.Add(3);sidecheck.Add(4);sidecheck.Add(5);
            for(int i=0;i<size;i++) 
            {
                for (int j = 0; j < size;j++ )
                {
                    sumside1[0] += this.up[i, j]; sumside2[0] += r.up[i, j];
                    sumside1[1] += this.left[i, j]; sumside2[1] += r.left[i, j];
                    sumside1[2] += this.face[i, j]; sumside2[2] += r.face[i, j];
                    sumside1[3] += this.right[i, j]; sumside2[3] += r.right[i, j];
                    sumside1[4] += this.back[i, j]; sumside2[4] += r.back[i, j];
                    sumside1[5] += this.down[i, j]; sumside2[5] += r.down[i, j];
                }
            }
            for(int i=0;i<6;i++) {
                for(int j=sidecheck.Count-1;j>=0;j--) {                    
                    if(sumside1[i]==sumside2[sidecheck[j]]) {
                        sidecheck.RemoveAt(j);
                        break;
                    }else if(sumside1[i]!=sumside2[sidecheck[j]] && j==0) {                                                
                        return false;
                    }
                }
            }
            return true;
        }

        public bool isEqualz(Rubiks r)
        {            
            Rubiks rup1 = createRubikCopy(r.rotateUp());
            Rubiks rup2 = createRubikCopy(r.rotateUp().rotateUp());
            Rubiks rup3 = createRubikCopy(r.rotateUp().rotateUp().rotateUp());

            if(isSame(this, r) || isSame(this,r.rotateRight()) || isSame(this, r.rotateRight().rotateRight()) || 
                isSame(this, r.rotateRight().rotateRight().rotateRight())) {
                    return true;
            }
            if(isSame(this,rup1) || isSame(this,rup1.rotateRight()) || isSame(this, rup1.rotateRight().rotateRight()) || isSame(this, rup1.rotateRight().rotateRight().rotateRight())) {
                return true;
            }
            if(isSame(this,rup2) || isSame(this,rup2.rotateRight()) || isSame(this,rup2.rotateRight().rotateRight()) || isSame(this, rup2.rotateRight().rotateRight().rotateRight()) ) {
                return true;
            }
            if(isSame(this,rup3) || isSame(this,rup3.rotateRight()) || isSame(this,rup3.rotateRight().rotateRight()) || isSame(this,rup3.rotateRight().rotateRight().rotateRight())) {
                return true;
            }
            return false;
        }

        //ngecek secara matriks ke matriks
        public static bool isSame(Rubiks r1, Rubiks r2)
        {
            if(!isEqualMat(r1.up,r2.up) || !isEqualMat(r1.left,r2.left) || !isEqualMat(r1.face,r2.face) 
                || !isEqualMat(r1.right,r2.right) || !isEqualMat(r1.back,r2.back) ||
                !isEqualMat(r1.down,r2.down)) {
                return false;
            }
            return true;
        }

        public static void copymat(char[,] src, char[,] dest)
        {                    
            for(int i=0;i<src.GetLength(0);i++) 
            {
                for (int j = 0; j < src.GetLength(1);j++ )
                {
                    dest[i,j] = src[i,j];    
                }
            }
        }

        //rotasi matrix (counter clockwise jika cc==true)
        private void rotatemat(char[,]mat, bool cc=false)
        {
            char[,] tempmat = new char[size, size];
            for (int i = 0; i < size; i++)
            {
                for (int j = 0; j < size; j++)
                {
                    tempmat[i,j] = mat[size-j-1,i];
                }
            }
            copymat(tempmat,mat);            
        }

        //beda dengan rotatemat, getRotatedMat tidak merubah src
        private char[,] getRotatedMat(char[,] src)
        {
            char[,] tempmat = new char[size, size];
            for (int i = 0; i < size; i++)
            {
                for (int j = 0; j < size; j++)
                {
                    tempmat[i, j] = src[size - j - 1, i];
                }
            }
            return tempmat;
        }

        public void testprint()
        {
            for(int i=0;i<size;i++)
            {
                for(int j=0;j<size*2;j++)
                {
                    if (i < size && j < size)
                    {
                        System.Diagnostics.Debug.Write("  ");
                    }
                    else
                    {                        
                        System.Diagnostics.Debug.Write(up[i,j-size] + " ");
                    }
                }
                System.Diagnostics.Debug.WriteLine("");
            }            
            for (int i = 0; i < size; i++)
            {
                for (int j = 0; j < size*4; j++)
                {
                    if(j < size) {
                        System.Diagnostics.Debug.Write(left[i,j] + " ");
                    }else if(j<2*size) {
                        System.Diagnostics.Debug.Write(face[i, j-size] + " ");
                    }else if(j<3*size) {
                        System.Diagnostics.Debug.Write(right[i, j-(2*size)] + " ");
                    }else if(j<4*size) {
                        System.Diagnostics.Debug.Write(back[i, j-(3*size)] + " ");
                    }
                }
                System.Diagnostics.Debug.WriteLine("");
            }            
            for (int i = 0; i < size; i++)
            {
                for (int j = 0; j < size*2; j++)
                {
                    if (i < size && j < size)
                    {
                        System.Diagnostics.Debug.Write("  ");
                    }
                    else
                    {
                        System.Diagnostics.Debug.Write(down[i, j - size] + " ");
                    }
                }
                System.Diagnostics.Debug.WriteLine("");
            }                        
        }

        public static Rubiks createRubikCopy(Rubiks src)
        {
            char[,] dUp = new char[src.size,src.size];
            char[,] dDown = new char[src.size, src.size];
            char[,] dFace = new char[src.size, src.size];
            char[,] dBack = new char[src.size, src.size];
            char[,] dLeft = new char[src.size, src.size];
            char[,] dRight = new char[src.size, src.size];
            copymat(src.up,dUp);
            copymat(src.down, dDown);
            copymat(src.left, dLeft);
            copymat(src.right, dRight);
            copymat(src.face, dFace);
            copymat(src.back, dBack);
            return new Rubiks(src.size, dUp, dDown, dFace, dBack, dLeft, dRight);
        }

        public bool isSolved()
        {
            char tface = face[0, 0];
            char tback = back[0, 0];
            char tup = up[0, 0];
            char tdown = down[0, 0];
            char tleft = left[0, 0];
            char tright = right[0, 0];
            for(int i=0;i<size;i++) {
                for (int j = 0; j < size;j++ )
                {
                    if ((tface != face[i, j]) || (tback!=back[i,j]) || (tup!=up[i,j]) || (tdown!=down[i,j]) || (tleft!=left[i,j]) || (tright!=right[i,j]))
                    {
                        return false;
                    }
                }
            }
            return true;
        }

        private static bool isEqualMat(char[,] m1, char[,] m2) {
            if(m1.GetLength(0)!=m2.GetLength(0) || m1.GetLength(1)!=m2.GetLength(1)) {
                return false;
            }
            for (int i = 0; i < m1.GetLength(0); i++)
            {
                for (int j = 0; j < m1.GetLength(1); j++)
                {
                    if(m1[i,j]!=m2[i,j]) {
                        return false;
                    }
                }
            }
            return true;
        }

        public Rubiks rotateUp()
        {
            char[,] rtop = new char[size,size];
            char[,] rleft = new char[size,size];
            char[,] rface = new char[size,size];
            char[,] rright = new char[size,size];
            char[,] rback = new char[size,size];
            char[,] rdown = new char[size,size];
            copymat(this.face,rtop);
            copymat(getRotatedMat(getRotatedMat(getRotatedMat(this.left))),rleft);
            copymat(this.down,rface);
            copymat(getRotatedMat(this.right),rright);
            copymat(hormirrormat(this.up),rback);
            copymat(getRotatedMat(getRotatedMat(this.back)),rdown);
            return new Rubiks(this.size, rtop, rdown, rface, rback, rleft, rright);
        }

        public Rubiks rotateRight()
        {
            char[,] rtop = new char[size, size];
            char[,] rleft = new char[size, size];
            char[,] rface = new char[size, size];
            char[,] rright = new char[size, size];
            char[,] rback = new char[size, size];
            char[,] rdown = new char[size, size];
            copymat(getRotatedMat(this.face), rtop);
            copymat(this.face, rleft);
            copymat(this.right, rface);
            copymat(this.back, rright);
            copymat(this.left, rback);
            copymat(getRotatedMat(getRotatedMat(getRotatedMat(this.down))), rdown);
            return new Rubiks(this.size, rtop, rdown, rface, rback, rleft, rright);
        }

        public Rubiks rotateClockw()
        {
            char[,] rtop = new char[size, size];
            char[,] rleft = new char[size, size];
            char[,] rface = new char[size, size];
            char[,] rright = new char[size, size];
            char[,] rback = new char[size, size];
            char[,] rdown = new char[size, size];
            copymat(getRotatedMat(this.left), rtop);
            copymat(getRotatedMat(this.down), rleft);
            copymat(getRotatedMat(this.face), rface);
            copymat(getRotatedMat(this.up), rright);
            copymat(getRotatedMat(getRotatedMat(getRotatedMat(this.back))), rback);
            copymat(getRotatedMat(this.right), rdown);
            return new Rubiks(this.size, rtop, rdown, rface, rback, rleft, rright);
        }

        //mengembalikan hasil vertical mirror matrix mat
        public char[,] vermirrormat(char[,] mat)
        {
            char[,] resmat = new char[mat.GetLength(0), mat.GetLength(1)];
            for(int i=0;i<mat.GetLength(0);i++) {
                for (int j = 0; j < mat.GetLength(1);j++ )
                {
                    resmat[i, j] = mat[i, mat.GetLength(1) - j - 1];
                }
            }            
            return resmat;
        }

        public char[,] hormirrormat(char[,] src)
        {
            char[,] resmat = new char[src.GetLength(0), src.GetLength(1)];
            for(int i=0;i<src.GetLength(0);i++) 
            {
                for (int j = 0; j < src.GetLength(1);j++ )
                {
                    resmat[i,j] = src[src.GetLength(0)-i-1,j];
                }
            }
            return resmat;
        }

        public static Rubiks createRubiksFromOpr(Rubiks r,List<int> lo)
        {
            Rubiks res = createRubikCopy(r);
            for(int i=0;i<lo.Count;i++) 
            {
                if(lo[i]==Rubiks.RIGHTC) {
                    res = res.getTurnRight();
                }else if(lo[i]==Rubiks.RIGHTCC) {
                    res = res.getTurnRight(true);
                }else if(lo[i]==Rubiks.UPC) {
                    res = res.getTurnUp();
                }else if(lo[i]==Rubiks.UPCC) {
                    res = res.getTurnUp(true);
                }else if(lo[i]==Rubiks.FACEC) {
                    res = res.getTurnFace();
                }else if(lo[i]==Rubiks.FACECC) {
                    res = res.getTurnFace(true);
                }
            }
            return res;
        }           
    }
}
